package com.gitee.wsl.time

import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.LocalTime
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlin.time.ExperimentalTime

/**
 * An enum representing the two periods of a 24-hour day for [ClockConvention.TWELVE_HOUR] clocks. Those two periods
 * are `a.m.` (from Latin "ante meridiem", translating to "before midday"), a.k.a. `AM`, and `p.m.` (from Latin
 * "post meridiem" translating to "after midday"), a.k.a. `PM`.
 *
 * An hour within the day can easily be converted to a 12-hour time with the associated [MeridiemPeriod]. Likewise, an
 * hour within the 12-hour time and its [MeridiemPeriod] can be converted to a 24-hour time.
 */
@Serializable
enum class MeridiemPeriod {

    @SerialName(value = "am")
    AM,

    @SerialName(value = "pm")
    PM;

}

val MeridiemPeriod.isAm: Boolean
    get() = this == MeridiemPeriod.AM

val MeridiemPeriod.isPm: Boolean
    get() = this == MeridiemPeriod.PM

/**
 * Retrieves the [LocalTime.hour] value within a [ClockConvention.TWELVE_HOUR] clock. This will return a value between
 * 1 and 12.
 */
@ExperimentalTime
val LocalTime.hourInTwelveHourClock: Int
    get() {
        val result = hour % 12

        return if (result == 0) 12 else result
    }

/**
 * Retrieves the [MeridiemPeriod] of this [LocalTime].
 *
 * Note that different conventions treat midnight and noon differently. Typically, midnight is considered
 * [MeridiemPeriod.AM] and noon is considered [MeridiemPeriod.PM], which is the default behavior of this function.
 * However, this is not always the case as sometimes they can be inverted. This function supports inverting the
 * midnight and noon [MeridiemPeriod] values by provided the [isMidnightAM] parameter value.
 */
@ExperimentalTime
fun LocalTime.meridiemPeriod(isMidnightAM: Boolean = true): MeridiemPeriod =
    if (isMidnightAM) {
        if (hour < 12) MeridiemPeriod.AM else MeridiemPeriod.PM
    } else {
        when {
            hour == 0 -> MeridiemPeriod.PM
            hour == 12 -> MeridiemPeriod.AM
            hour < 12 -> MeridiemPeriod.AM
            else -> MeridiemPeriod.PM
        }
    }

/**
 * A convenience function for testing whether this [LocalTime.meridiemPeriod] is [MeridiemPeriod.AM].
 */
@ExperimentalTime
fun LocalTime.isAm(isMidnightAM: Boolean = true): Boolean = meridiemPeriod(isMidnightAM = isMidnightAM).isAm

/**
 * A convenience function for testing whether this [LocalTime.meridiemPeriod] is [MeridiemPeriod.PM].
 */
@ExperimentalTime
fun LocalTime.isPm(isMidnightAM: Boolean = true): Boolean = meridiemPeriod(isMidnightAM = isMidnightAM).isPm

/**
 * Retrieves the [MeridiemPeriod] of this [LocalDateTime].
 *
 * Note that different conventions treat midnight and noon differently. Typically, midnight is considered
 * [MeridiemPeriod.AM] and noon is considered [MeridiemPeriod.PM], which is the default behavior of this function.
 * However, this is not always the case as sometimes they can be inverted. This function supports inverting the
 * midnight and noon [MeridiemPeriod] values by provided the [isMidnightAM] parameter value.
 */
@ExperimentalTime
fun LocalDateTime.meridiemPeriod(isMidnightAM: Boolean = true): MeridiemPeriod =
    if (isMidnightAM) {
        if (hour < 12) MeridiemPeriod.AM else MeridiemPeriod.PM
    } else {
        when {
            hour == 0 -> MeridiemPeriod.PM
            hour == 12 -> MeridiemPeriod.AM
            hour < 12 -> MeridiemPeriod.AM
            else -> MeridiemPeriod.PM
        }
    }

/**
 * A convenience function for testing whether this [LocalDateTime.meridiemPeriod] is [MeridiemPeriod.AM].
 */
@ExperimentalTime
fun LocalDateTime.isAm(isMidnightAM: Boolean = true): Boolean = meridiemPeriod(isMidnightAM = isMidnightAM).isAm

/**
 * A convenience function for testing whether this [LocalDateTime.meridiemPeriod] is [MeridiemPeriod.PM].
 */
@ExperimentalTime
fun LocalDateTime.isPm(isMidnightAM: Boolean = true): Boolean = meridiemPeriod(isMidnightAM = isMidnightAM).isPm

